﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using OpenFileTransfer.Util.Extensions;

namespace OpenFileTransfer.Util.Net
{
    public class UdpClientHelper
    {
        public event Action<string, int, byte[]> DataReceived = (ip, port, data) => { };
        public event Action<string> ErrorOccurred = (message) => { };

        private Task _task;
        private CancellationTokenSource _tokenSource;
        private UdpClient _udpClient;

        private bool _receiveDataError;

        public void Start(int bindingPort)
        {
            try
            {
                _udpClient = new UdpClient();

                //绑定端口
                var ipEndPoint = new IPEndPoint(IPAddress.Any, bindingPort);
                _udpClient.Client.Bind(ipEndPoint);
            }
            catch (Exception e)
            {
                ErrorOccurred($@"UdpClient绑定端口[{bindingPort}]异常：{e.Message}.");
                DisposeUdpClient();
                _udpClient = null;
                return;
            }

            _tokenSource = new CancellationTokenSource();
            _task = new Task(() => ReceiveDataAsync(_tokenSource.Token).Wait());
            _task.Start();
        }

        public void Stop()
        {
            _tokenSource?.Cancel(false);
            DisposeUdpClient();

            _udpClient = null;
            _task?.Wait();
            _task = null;
            _tokenSource?.Dispose();
            _tokenSource = null;
        }

        public async Task SendAsync(string ip, int port, byte[] data)
        {
            if (_udpClient == null)
            {
                return;
            }

            try
            {
                await _udpClient.SendAsync(data, data.Length, ip, port);
            }
            catch (Exception e)
            {
                var message = $@"UdpClient发送数据异常：target = {ip}:{port}, {e.Message}.";
                ErrorOccurred?.Invoke(message);
            }
        }

        public async Task BroadcastAsync(int port, byte[] data)
        {
            await SendAsync("255.255.255.255", port, data);
        }

        private async Task ReceiveDataAsync(CancellationToken token)
        {
            while (true)
            {
                if (IsCancellationRequested(token))
                {
                    break;
                }

                var delayTime = 1;
                try
                {
                    var ret = await _udpClient.ReceiveAsync();
                    _receiveDataError = false;
                    DataReceived?.Invoke($"{ret.RemoteEndPoint.Address}", ret.RemoteEndPoint.Port, ret.Buffer);
                }
                catch (InvalidOperationException)
                {
                    //没有建立连接
                }
                catch (Exception e)
                {
                    if (!_receiveDataError)
                    {
                        _receiveDataError = true;
                        delayTime = 1000;
                        ErrorOccurred?.Invoke($@"UdpClient接收异常：{e.Message}.");
                    }
                }

                try
                {
                    //暂停接收UDP数据
                    await Task.Delay(delayTime, token);
                }
                catch (Exception)
                {
                    break;
                }
            }
        }

        private void DisposeUdpClient()
        {
            try
            {
                try
                {
                    _udpClient?.Client.Shutdown(SocketShutdown.Both);
                }
                catch (Exception)
                {
                    // ignored
                }

                _udpClient?.Close();
                _udpClient?.Dispose();
            }
            catch (Exception e)
            {
                ErrorOccurred?.Invoke($@"关闭UdpClient异常：{e.Message}.");
            }
        }

        private bool IsCancellationRequested(CancellationToken token)
        {
            try
            {
                return token.IsCancellationRequested;
            }
            catch (Exception e)
            {
                ErrorOccurred?.Invoke($@"UdpService Token异常：{e.GetFullMessage()}.");
                return true;
            }
        }
    }
}
